package edu.gatech.cs4220;

import android.location.Location;
import java.util.Set;
import android.content.Context;


//FIXME add preferences set
//XXX recfactor to more general generic constraints model 
public class LocationProfile implements java.io.Serializable
{
	private static final long serialVersionUID = 8934521971209115305L;
	
	private Set<LocationProfilePoint> points;
	ProfileData settings;
	boolean enabled = false;
	private final String name;
	
	public LocationProfile(final Context APP_CONTEXT, String name)
	{
		points = new java.util.HashSet<LocationProfilePoint>();
		settings = new ProfileData(APP_CONTEXT);
		this.name = name;
	}
	
	public LocationProfile(java.util.Collection<LocationProfilePoint> points, final Context APP_CONTEXT, String name)
	{
		this.points = new java.util.HashSet<LocationProfilePoint>(points);
		settings = new ProfileData(APP_CONTEXT);
		this.name = name;
	}
	
	public LocationProfile(LocationProfilePoint[] points, final Context APP_CONTEXT, String name)
	{
		this.points = new java.util.HashSet<LocationProfilePoint>(java.util.Arrays.asList(points));
		settings = new ProfileData(APP_CONTEXT);
		this.name = name;
	}
	
	private LocationProfile(LocationProfile toCopy, String newName)
	{
		points = toCopy.points;
		settings = toCopy.settings;
		enabled = toCopy.enabled;
		name = newName;
	}
	
	public synchronized boolean getEnabled()
	{
		return enabled;
	}
	
	public final String getName()
	{
		return name;
	}
	
	public synchronized boolean setEnabled(boolean enabled)
	{
		boolean oldVal = this.enabled;
		this.enabled = enabled;
		return oldVal;
	}
	
	public synchronized boolean setEnabled()
	{
		return setEnabled(true);
	}
	
	/**
	 * Returns whether the conditions based on constraints that can be verified in constant time using only local members of each constraint or
	 * globally accessable parameters that can be accessed reliebly.
	 * Contraints that cannot be tested this way must be evaluated separately.
	 * @return
	 */
	public boolean quickIsElegible()
	{
		//FIXME add time and enabled flag
		return getEnabled();
	}
	
	static boolean isLocationInPoint(Location l, LocationProfilePoint lInThis)
	{
		float[] result = new float[1];
		Location.distanceBetween(l.getLatitude(), l.getLongitude(), lInThis.latitude, lInThis.longitude, result);
		return result[0] < lInThis.accuracy;
	}
	
	static boolean isLocationInPoint(LocationProfilePoint l, LocationProfilePoint lInThis)
	{
		float[] result = new float[1];
		Location.distanceBetween(l.latitude, l.longitude, lInThis.latitude, lInThis.longitude, result);
		return result[0] < lInThis.accuracy;
	}
	
	
	public float getClosestDistanceToLocation(Location l)
	{
		return getClosestDistanceToLocation(new LocationProfilePoint(l));
	}
	
	float getClosestDistanceToLocation(LocationProfilePoint l)
	{
		//First test to see if this exact  point is in the set already
		if(points.contains(l)) return 0;
		float result = Float.NEGATIVE_INFINITY;
		float[] temp = new float[0];
		for(LocationProfilePoint point : points)
		{
			Location.distanceBetween(l.latitude, l.longitude, point.latitude, point.longitude, temp);
			if(result < temp[0])
				result = temp[0];
		}
		return result;
	}
	
	public boolean isLocationInProfile(Location l)
	{
		for(LocationProfilePoint point : points)
		{
			if(isLocationInPoint(l, point))
			{
				return true;
			}
		}
		return false;
	}
	
	boolean isLocationInProfile(LocationProfilePoint l)
	{
		for(LocationProfilePoint point : points)
		{
			if(isLocationInPoint(l, point))
			{
				return true;
			}
		}
		return false;
	}
	
	public boolean containsPoint(Location l)
	{
		return containsPoint(LocationProfilePoint.getInstance(l));
	}
	
	boolean containsPoint(LocationProfilePoint l)
	{
		return points.contains(l);
	}
	
	public boolean addPoint(Location l)
	{
		return addPoint(LocationProfilePoint.getInstance(l));
	}
	
	boolean addPoint(LocationProfilePoint l)
	{
		return points.add(l);
	}
	
	//FIXME a way to get all the points of this set in a publically exposable way (like a set of Location points or something) 
	
	Set<LocationProfilePoint> getPointSet() 
	{
		return java.util.Collections.unmodifiableSet(points);
	}
	
	ProfileData getSettings()
	{
		return settings;
	}
	
	/**
	 * Returns a new LocationProfile that has the same internals but different name
	 * DO NOT USE THIS IF THIS IS IN A LocationProfileSet, as it needs to maintain a name to profile map.
	 * Use {@link LocationProfileSet#renameProfile(String, String)} instead
	 * @param newName
	 */
	LocationProfile getNewWithChangedName(String newName)
	{
		return new LocationProfile(this, newName);
	}
	
}
